#include "ngram_table.h"

#include <cstdio>
#include <cstdlib>
#include <string>

#include <gtest/gtest.h>

#include <unistd.h>

namespace anagram_optimizer {
namespace {

std::string GetTemporaryFilenameOrDie() {
  char filename[L_tmpnam];
  if (tmpnam(filename) == NULL) {
    fprintf(stderr, "Unable to generate temporary file.\n");
    abort();
  }
  return filename;
}

std::string WriteTestFile(const char* contents) {
  std::string filename = GetTemporaryFilenameOrDie();
  FILE* fp = fopen(filename.c_str(), "w");
  if (fp == NULL || fputs(contents, fp) < 0) {
    fprintf(stderr, "Unable to write file.\n");
    abort();
  }
  fclose(fp);
  return filename;
}

std::string ReadAndUnlinkFile(const std::string& filename) {
  FILE* fp = fopen(filename.c_str(), "r");
  if (fp == NULL) {
    fprintf(stderr, "Unable to read file.\n");
    abort();
  }
  char buf[2048];
  std::string ret;
  while (fgets(buf, 2048, fp)) ret += buf;
  unlink(filename.c_str());
  return ret;
}

TEST(NGramTableTest, StoresNGramFrequency) {
  NGram<3> ngram{{"new", "year's", "eve"}};
  NGramTable<3> table;
  table.Add(ngram, 1);
  table.Add(ngram, 2);
  EXPECT_EQ(3, table.Count(ngram));
}

TEST(NGramTableTest, ReadsTestFile) {
  std::string filename = WriteTestFile(
      "new year's eve 10\n"
      " white  \t  space   \t  \tsucks 1 \n"
      "\n"
      "trol\tlol ol 1000\n");
  NGramTable<3> table;
  bool success = table.Read(filename);
  unlink(filename.c_str());
  ASSERT_TRUE(success);
  EXPECT_EQ(10, table.Count(NGram<3>{{"new", "year's", "eve"}}));
  EXPECT_EQ(1, table.Count(NGram<3>{{"white", "space", "sucks"}}));
  EXPECT_EQ(1000, table.Count(NGram<3>{{"trol", "lol", "ol"}}));
}

TEST(NGramTableTest, ParseSumsDupesWhenInstructed) {
  std::string filename = WriteTestFile(
      "new year's eve 1\n"
      "new year's eve 2\n");
  NGramTable<3> table;
  bool success = table.Read(filename, NGramTable<3>::kSumFrequencies);
  unlink(filename.c_str());
  ASSERT_TRUE(success);
  EXPECT_EQ(3, table.Count(NGram<3>{{"new", "year's", "eve"}}));
}

TEST(NGramTableTest, ParseFailsOnDupes) {
  std::string filename = WriteTestFile(
      "new year's eve 1\n"
      "new year's eve 2\n");
  NGramTable<3> table;
  EXPECT_FALSE(table.Read(filename));
  unlink(filename.c_str());
}

TEST(NGramTableTest, ParseFailsOnShortLines) {
  std::string filename = WriteTestFile(
      "new year's\n");
  NGramTable<3> table;
  EXPECT_FALSE(table.Read(filename));
  unlink(filename.c_str());
}

TEST(NGramTableTest, ParseFailsOnLongLines) {
  std::string filename = WriteTestFile(
      "new year's eve eve 0\n");
  NGramTable<3> table;
  EXPECT_FALSE(table.Read(filename));
  unlink(filename.c_str());
}

TEST(NGramTableTest, ParseFailsNegativeFrequency) {
  std::string filename = WriteTestFile(
      "new year's eve -10\n");
  NGramTable<3> table;
  EXPECT_FALSE(table.Read(filename));
  unlink(filename.c_str());
}

TEST(NGramTableTest, ParseFailsFractionalFrequency) {
  std::string filename = WriteTestFile(
      "new year's eve 1.2\n");
  NGramTable<3> table;
  EXPECT_FALSE(table.Read(filename));
  unlink(filename.c_str());
}

TEST(NGramTableTest, ParseFailsBogusFrequency) {
  std::string filename = WriteTestFile(
      "new year's eve asdf\n");
  NGramTable<3> table;
  EXPECT_FALSE(table.Read(filename));
  unlink(filename.c_str());
}

TEST(NGramTableTest, ParsesFileWithLongLines) {
  std::string filename = WriteTestFile(
      "this_is_a_long_line_with_more_than_512_chars_00000000000000000000000"
      "00000000000000000000000000000000000000000000000000000000000000000000"
      "00000000000000000000000000000000000000000000000000000000000000000000"
      "00000000000000000000000000000000000000000000000000000000000000000000"
      "00000000000000000000000000000000000000000000000000000000000000000000"
      "00000000000000000000000000000000000000000000000000000000000000000000"
      "00000000000000000000000000000000000000000000000000000000000000000000"
      "00000000000000 u mad? 100\n");
  NGramTable<3> table;
  EXPECT_TRUE(table.Read(filename));
  unlink(filename.c_str());
}

TEST(NGramTableTest, ParsesFileWith511CharacterLine) {
  // Heh heh ...
  std::string filename = WriteTestFile(
      "123456789012345678901234567890123456789012345678901234567890"  // 60
      "123456789012345678901234567890123456789012345678901234567890"  // 120
      "123456789012345678901234567890123456789012345678901234567890"  // 180
      "123456789012345678901234567890123456789012345678901234567890"  // 240
      "123456789012345678901234567890123456789012345678901234567890"  // 300
      "123456789012345678901234567890123456789012345678901234567890"  // 360
      "123456789012345678901234567890123456789012345678901234567890"  // 420
      "123456789012345678901234567890123456789012345678901234567890"  // 480
      "123456789012345678901234 6 8 1\n"
      "a b c 1\n");
  NGramTable<3> table;
  EXPECT_TRUE(table.Read(filename));
  unlink(filename.c_str());

}

TEST(NGramTableTest, WritesFile) {
  std::string filename = GetTemporaryFilenameOrDie();
  NGramTable<3> table;
  table.Add(NGram<3>{{"new", "year's", "eve"}}, 10);
  table.Add(NGram<3>{{"three", "word", "mission"}}, 3);
  EXPECT_TRUE(table.Write(filename));
  const std::string expected_lines[] = {
      "new year's eve 10\n",
      "three word mission 3\n"};
  const std::string valid_contents[] = {
      expected_lines[0] + expected_lines[1],
      expected_lines[1] + expected_lines[0]};
  const std::string actual_contents = ReadAndUnlinkFile(filename);
  EXPECT_TRUE(actual_contents == valid_contents[0] ||
              actual_contents == valid_contents[1])
      << "Actual contents were \"" << actual_contents << "\", but "
      << "expected some permutation of \"" << valid_contents[0] << "\".";
}

}  // namespace
}  // namespace anagram_optimizer
